/*
 * Copyright (c) 2017-present, CV4J Contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cv4j.core.filters;

import com.cv4j.core.datamodel.ColorProcessor;
import com.cv4j.core.datamodel.ImageProcessor;
import com.cv4j.core.datamodel.IntIntegralImage;

import java.util.Optional;

/**
 * 油漆过滤
 *
 * @author dev
 * @date 2021/07/22
 */
public class OilPaintFilter extends BaseFilter {
    private int ksize = 15; // default value
    private float intensity = 30; // default value

    /**
     * 油漆过滤
     */
    public OilPaintFilter() {
        this(15, 40);
    }

    /**
     * 油漆过滤
     *
     * @param radius 半径
     * @param graylevel 灰度
     */
    public OilPaintFilter(int radius, int graylevel) {
        this.ksize = radius;
        this.intensity = graylevel;
    }

    /**
     * 得到的块大小
     *
     * @return int
     */
    public int getBlockSize() {
        return ksize;
    }

    /**
     * 组块大小
     *
     * @param ksize ksize
     */
    public void setBlockSize(int ksize) {
        this.ksize = ksize;
    }

    /**
     * 得到的强度
     *
     * @return float
     */
    public float getIntensity() {
        return intensity;
    }

    /**
     * 设置强度
     *
     * @param intensity 强度
     */
    public void setIntensity(int intensity) {
        this.intensity = intensity;
    }

    /**
     * 做的过滤器
     *
     * @param src src
     * @return {@link Optional<ImageProcessor>}
     */
    @Override
    public Optional<ImageProcessor> doFilter(ImageProcessor src) {
        // initialization parameters
        int width = src.getWidth();
        int height = src.getHeight();

        // start ep process
        IntIntegralImage redii = new IntIntegralImage();
        IntIntegralImage greenii = new IntIntegralImage();
        IntIntegralImage blueii = new IntIntegralImage();

        redii.setImage(src.toByte(0));
        greenii.setImage(src.toByte(1));
        blueii.setImage(src.toByte(2));

        redii.calculate(width, height);
        greenii.calculate(width, height);
        blueii.calculate(width, height);

        byte[][] output = getBytes(src, redii, greenii, blueii);
        // release memory
        ((ColorProcessor) src).putRGB(output[0], output[1], output[2]);
        output = null;
        return Optional.ofNullable(src);
    }

    private byte[][] getBytes(ImageProcessor src, IntIntegralImage redii, IntIntegralImage greenii, IntIntegralImage blueii) {
        int width = src.getWidth();
        int height = src.getHeight();
        int x2 = 0;
        int y2 = 0;
        int x1 = 0;
        int y1 = 0;
        int cx = 0;
        int cy = 0;
        int radius = ksize / 2;
        int sr = 0;
        int sg = 0;
        int sb = 0;
        int red = 0;
        int greed = 0;
        int blue = 0;
        float mr = 0;
        int mg = 0;
        int mb = 0;
        byte[][] output = new byte[3][RED.length];
        int index = 0;
        for (int row = 0; row < height + radius; row++) {
            y2 = (row + 1) > height ? height : (row + 1);
            y1 = (row - ksize) < 0 ? 0 : (row - ksize);
            for (int col = 0; col < width + radius; col++) {
                x2 = (col + 1) > width ? width : (col + 1);
                x1 = (col - ksize) < 0 ? 0 : (col - ksize);
                cx = (col - radius) < 0 ? 0 : col - radius;
                cy = (row - radius) < 0 ? 0 : row - radius;
                int num = (x2 - x1) * (y2 - y1);
                sr = redii.getBlockSum(x1, y1, x2, y2);
                sg = greenii.getBlockSum(x1, y1, x2, y2);
                sb = blueii.getBlockSum(x1, y1, x2, y2);
                mr = sr / num;
                mg = sg / num;
                mb = sb / num;
                index = cy * width + cx;
                red = src.toByte(0)[index] & 0xff;
                greed = src.toByte(1)[index] & 0xff;
                blue = src.toByte(2)[index] & 0xff;
                float delta = (mr + mg + mb) - (red + greed + blue);
                if (delta > intensity) {
                    output[0][index] = (byte) red;
                    output[1][index] = (byte) greed;
                    output[2][index] = (byte) blue;
                } else {
                    output[0][index] = (byte) mr;
                    output[1][index] = (byte) mg;
                    output[2][index] = (byte) mb;
                }
            }
        }
        return output;
    }
}
